package com.whz.generic.parameter;

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 按照参数名称(如 'name')和索引(如 "p0' 为第一个参数,'p1'为第二个参数)反射查询方法的参数值
 *
 * @author whz
 *  2021/8/13
 */
public class ParameterDiscoverer {

    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final Map<String, Object> variables = new ConcurrentHashMap<>();

    private final Method method;
    private final Object[] arguments;

    private boolean argumentsLoaded = false;

    public ParameterDiscoverer(Method method, Object[] arguments) {
        this.method = method;
        this.arguments = arguments;
    }

    /**
     * 按照参数名称和索引查询参数值
     *
     * @param name 参数名称或索引(p0,p1,p2...变化参数是最后一个)
     * @return 参数值
     */
    public Object lookupVariable(String name) {
        Object variable = variables.get(name);
        if (!argumentsLoaded) {
            lazyLoadArguments();
            argumentsLoaded = true;
            variable = variables.get(name);
        }
        return variable;
    }

    /**
     * Load the param information only when needed.
     */
    protected void lazyLoadArguments() {
        // Shortcut if no args need to be loaded
        if (ObjectUtils.isEmpty(arguments)) {
            return;
        }

        // Expose indexed variables as well as parameter names (if discoverable)
        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
        int paramCount = (paramNames != null ? paramNames.length : method.getParameterCount());
        int argsCount = arguments.length;

        for (int i = 0; i < paramCount; i++) {
            Object value = null;
            if (argsCount > paramCount && i == paramCount - 1) {
                // Expose remaining arguments as vararg array for last parameter
                value = Arrays.copyOfRange(arguments, i, argsCount);
            } else if (argsCount > i) {
                // Actual argument found - otherwise left as null
                value = arguments[i];
            }
            setVariable("p" + i, value);
            setVariable("P" + i, value);
            if (paramNames != null) {
                setVariable(paramNames[i], value);
            }
        }
    }

    private void setVariable(String name, @Nullable Object value) {
        if (value != null) {
            variables.put(name, value);
        } else {
            variables.remove(name);
        }
    }
}
